/*
 * @Author: Leung
 * @Date: 2025-07-21 19:51:16
 * @LastEditTime: 2025-08-06 23:50:05
 * @FilePath: \Projecte:\STM32F103ZET6\Peripheral modules\EEPROM_AT24C02\I2C_Soft\User\i2c\bsp_i2c_ee.c
 * @Description: 
 * 
 * Copyright (c) 2025 by ${git_name_email}, All Rights Reserved. 
 */
#include "./i2c/bsp_i2c_ee.h"
#include "./i2c/bsp_i2c_gpio.h"
#include "./usart/bsp_usart.h"


/**
 * @brief: 判断串行EEPROM是否正常
 * @retval: 1 表示正常， 0 表示不正常
 */
uint8_t ee_CheckOk(void)
{
    if (i2c_CheckDevice(EEPROM_DEV_ADDR) == 0)
    {
        return 1;   // 返回1表示正常
    }
    else
    {
        /*失败后，切记发送I2C总线停止信号*/
        i2c_Stop();
        return 0;   // 返回0表示不正常
    }
}


/**
 * @brief: 从串行EEPROM指定地址处开始读取若干数据
 * @param {uint8_t} *_pReadBuf 存放读到的数据的缓冲区指针
 * @param {uint16_t} _usAddress 起始地址
 * @param {uint16_t} _usSize 数据长度，单位为字节
 * @retval: 
 */
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
    uint16_t i;

    /**
     * 采用串行EEPROM随即读取指令序列，连续读取若干字节
     * 时序由两个部分组成
     * 第一部分（写操作）：Start->发送设备地址+W(0xA0)->Wait ACK->发送要读取的内存地址(Word Address)->Wait ACK
     * 第二部分（读操作）：Restart(重发Start)->发送设备地址+R(0xA1)->Wait ACK->读取一个字节->发送NACK(因为只读一个)->Stop
     */
    // 第一部分（写操作）
    // 1.发送起始信号Start
    i2c_Start();

    // 2.发送设备地址+W(0XA0)
    i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR);   // 高7bit为地址，bit0是读写控制位，0为写WR，1为读RD

    // 3.等到从机应答Wait ACK
    if(i2c_WaitAck() != 0)
    {
        goto cmd_fail;      // EEPROM器件无应答
    }

    // 4.发送要读取的字节内存地址(Word Adderss)
    i2c_SendByte((uint8_t)_usAddress);

    // 5.等到从机应答Wait ACK
    if(i2c_WaitAck() != 0)
    {
        goto cmd_fail;      // EEPROM器件无应答
    }

    // 第二部分（读操作）
    // 1.再次发送起始信号Start
    i2c_Start();

    // 2.发送设备地址+R(0XA1)
    i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD);     // 高7bit为数据位，低bit0为1即 读 位RD

    // 3.等到从机应答Wait ACK
    if(i2c_WaitAck() != 0)
    {
        goto cmd_fail;      // EEPROM器件无应答
    }

    // 4.读取一个字节，循环读取数据
    for (i = 0; i < _usSize; i++)
    {
        _pReadBuf[i] = i2c_ReadByte();  // 读取一个字节
        /* 每读完1个字节后，需要发送Ack，最后一个字节只需要发Nack */
        if (i != _usSize - 1)
        {
            i2c_Ack();  // 读完1个字节后CPU产生ACK信号(驱动SDA = 0)
        }
        else    // 5.发送NACK(因为只读一个)
        {
            i2c_NAck(); // 读完最后一个字节后CPU产生NACK信号(驱动SDA = 1)
        }    
    }
    
    /* 6.发送I2C总线停止信号 */
    i2c_Stop();
    return 1;   // 执行成功

cmd_fail:   // 命令执行失败后，切记发送停止信号，避免影响I2C总线上其他设备
    /* 发送I2C总线停止信号 */
    i2c_Stop();
    return 0;
}



/**
 * @brief: 向串行EEPROM指定地址写入若干数据，采用页写操作提高写入效率
 * @param {uint8_t} *_pWriteBuf 存放读到的数据的缓冲区指针
 * @param {uint16_t} _usAddress 起始地址
 * @param {uint16_t} _usSize 数据长度，单位为字节
 * @retval: 0 表示失败，1表示成功
 */
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i, m;
	uint16_t usAddr;
	
	/**
	 * 写串行EEPROM不像读操作可以连续读取很多字节，每次写操作只能在同一个page。
	 * 对于24xx02，page size = 8
	 * 简单的处理方法为：按字节写操作模式，没写1个字节，都发送地址
	 * 为了提高连续写的效率: 本函数采用page wirte操作。
	 */
	usAddr = _usAddress;	
	for (i = 0; i < _usSize; i++)
	{
		/* 当发送第1个字节或是页面首地址时，需要重新发起启动信号和地址 */
		if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
		{
			/*　第０步：发停止信号，启动内部写操作　*/
			i2c_Stop();
			
			/* 通过检查器件应答的方式，判断内部写操作是否完成, 一般小于 10ms 			
				CLK频率为200KHz时，查询次数为30次左右
			*/
			for (m = 0; m < 1000; m++)
			{				
				/* 第1步：发起I2C总线启动信号 */
				i2c_Start();
				
				/* 第2步：发起控制字节，高7bit是地址，bit0是读写控制位，0表示写，1表示读 */
				i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR);	/* 此处是写指令 */
				
				/* 第3步：发送一个时钟，判断器件是否正确应答 */
				if (i2c_WaitAck() == 0) break;
			}
			if (m  == 1000) goto cmd_fail;	/* EEPROM器件写超时 */
		
			/* 第4步：发送字节地址，24C02只有256字节，因此1个字节就够了，如果是24C04以上，那么此处需要连发多个地址 */
			i2c_SendByte((uint8_t)usAddr);
			
			/* 第5步：等待ACK */
			if (i2c_WaitAck() != 0) goto cmd_fail;	/* EEPROM器件无应答 */
		}
	
		/* 第6步：开始写入数据 */
		i2c_SendByte(_pWriteBuf[i]);
	
		/* 第7步：发送ACK */
		if (i2c_WaitAck() != 0) goto cmd_fail;	/* EEPROM器件无应答 */

		usAddr++;	/* 地址增1 */		
	}
	
	/* 命令执行成功，发送I2C总线停止信号 */
	i2c_Stop();
	return 1;

cmd_fail: /* 命令执行失败后，切记发送停止信号，避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;
}


void ee_Erase(void)
{
	uint16_t i;
	uint8_t buf[EEPROM_SIZE];
	
	/* 填充缓冲区 */
	for (i = 0; i < EEPROM_SIZE; i++)
	{
		buf[i] = 0xFF;
	}
	
	/* 写EEPROM, 起始地址 = 0，数据长度为 256 */
	if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)
	{
		printf("擦除eeprom出错！\r\n");
		return;
	}
	else
	{
		printf("擦除eeprom成功！\r\n");
	}
}


/*--------------------------------------------------------------------------------------------------*/
// 简单延时函数
static void ee_Delay(__IO uint32_t nCount)
{
	for (; nCount != 0; nCount--);
}

uint8_t ee_Test(void)
{
	uint16_t i;
	static uint8_t write_buf[EEPROM_SIZE];
	static uint8_t read_buf[EEPROM_SIZE];	// 使用static防止栈溢出

/*--------------------------------------------------------------------------------------------------*/
	// 检测有没有串行EEPROM
	if (ee_CheckOk() == 0)
	{
		/* 没有检测到EEPROM */
		printf("Serial EEPROM not detected!\r\n");
		return 0;
	}
/*--------------------------------------------------------------------------------------------------*/	
	// 填充测试缓冲区
	for (i = 0; i < EEPROM_SIZE; i++)
	{
		write_buf[i] = i;
	}
/*--------------------------------------------------------------------------------------------------*/
	// 判断是否正常写入
	if (ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)
	{
		printf("Write EEPROM error!\r\n");
		return 0;
	}
	else
	{
		printf("Write EEPROM successfully!\r\n");
	}

	// 写完后做适当延时再读取，等待I2C总线空闲
	ee_Delay(0x0FFFFF);
/*--------------------------------------------------------------------------------------------------*/
	// 判断是否正常读取
	if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
	{
		printf("Read EEPROM error!\r\n");
		return 0;
	}
	else
	{
		printf("Read EEPROM successfully, data with the follows:\r\n");
	}
/*--------------------------------------------------------------------------------------------------*/	
	// 轮询判断读取数据与写入数据是否一致
	for (i = 0; i < EEPROM_SIZE; i++)
	{
		if (read_buf[i] != write_buf[i])
		{
			printf("Error: Inconsistent data read and written from EEPROM!\r\n");
			printf("Error at Adrr: 0x%04X, Expected: 0x%02X, Read: 0x%02X\r\n", i , write_buf[i], read_buf[i]);
			return 0;	// 发现错误立即返回
		}
		printf(" %02X", read_buf[i]);

		if ((i & 15) == 15)
		{
			printf("\r\n");
		}
	}
	printf("\r\n\r\nEEPROM read and written test successfully!\r\n");
	return 1;
}
/*********************************************END OF FILE**********************/
